﻿using System;
using System.IO;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;
using System.Text;
using Microsoft.Ajax.Utilities;
using LinqExpression = System.Linq.Expressions.Expression;
using System.Diagnostics.Contracts;
using System.Reflection.Emit;
using System.Reflection;

namespace SimpleData.Infrastructure.JavaScript
{
	/// <summary>
	/// Realiza a compilação do código javascript para .net
	/// </summary>
	public sealed class JavaScriptCompiler
	{
		private AssemblyBuilder asmBuilder;
		private ModuleBuilder modBuilder;

		/// <summary>
		/// 
		/// </summary>
		public JavaScriptCompiler()
		{
			this.Init();

			this.EmitAssembly = true;
			this.AssemblyName = "JsSample";
		}

		/// <summary>
		/// Indica se deve ser incluida as informações de DEBUG do código
		/// </summary>
		public bool IncludeDebugInfo
		{ get; set; }

		/// <summary>
		/// 
		/// </summary>
		public bool EmitAssembly
		{ get; set; }

		/// <summary>
		/// 
		/// </summary>
		public string AssemblyName
		{ get; set; }

		/// <summary>
		/// Obtem ou define as configurações do compilador JavaScript
		/// </summary>
		public CompilerSettings Settings
		{ get; private set; }

		/// <summary>
		/// Inicializa o compilador com as configurações default
		/// </summary>
		private void Init()
		{
			this.Settings = new CompilerSettings();
		
#if DEBUG
			this.IncludeDebugInfo = true;
#endif

			if ( this.EmitAssembly )
			{
				this.asmBuilder = AppDomain.CurrentDomain.DefineDynamicAssembly( new AssemblyName( this.AssemblyName ), AssemblyBuilderAccess.RunAndSave );
				//this.modBuilder = this.asmBuilder.DefineDynamicModule( "MyJsSample", this.IncludeDebugInfo );
			}
		}

		/// <summary>
		/// Realiza a compilação do código <paramref name="source"/>
		/// </summary>
		/// <param name="source"></param>
		/// <returns></returns>
		public JavaScriptCode Compile( string source )
		{
			JSParser parser = new JSParser( source );
			CodeSettings settings = parser.Settings;

			settings.CollapseToLiteral = true;
			settings.EvalLiteralExpressions = true;
			settings.LocalRenaming = LocalRenaming.KeepAll;
			settings.MinifyCode = false;
			settings.PreserveFunctionNames = true;
			settings.RemoveUnneededCode = false;

			var block = parser.Parse( settings );
			var builder = new JavaScriptExpressionBuilder();

			if ( this.EmitAssembly )
				builder.SetModuleBuilder( this.asmBuilder.DefineDynamicModule( "MyModSample" ) );
			builder.Parse( block );	
			//if ( this.IncludeDebugInfo )
			//{
			//    // Gera o arquivo temporário que será utilizado para debugar
			//    File.WriteAllText( Path.Combine( this.Settings.TempPath, this.GetRandomFileName() ), block.ToCode() );
			//}

			return builder.Code;
		}

		public JavaScriptCode Compile( TextReader codeReader )
		{
			Contract.Requires<ArgumentNullException>( codeReader != null );

			return this.Compile( codeReader.ReadToEnd() );
		}

		public JavaScriptCode Compile( Stream codeStream)
		{
			return this.Compile( new StreamReader( codeStream ) );
		}

		public JavaScriptCode CompileFromFile( string filePath )
		{
			return this.Compile( File.OpenRead( filePath ) );
		}



		private string GetRandomFileName()
		{
			return "blah.js";
		}

		#region class CompilerSettings

		/// <summary>
		/// Informações do assembly que será gerado quando 
		/// </summary>
		public class CompilerSettings
		{

			public CompilerSettings()
			{
				// TODO : Inicializar as variáveis
			}

			/// <summary>
			/// Obtém ou define o namespace padrão do assembly
			/// </summary>
			public string Namespace
			{ get; set; }

			/// <summary>
			/// Obtém ou define o caminho absoluto da pasta onde ficará salvo o assembly
			/// </summary>
			public string BasePath
			{ get; set; }

			/// <summary>
			/// Obtém ou define o caminho absoluto da pasta temporária
			/// </summary>
			public string TempPath
			{ get; set; }

			/// <summary>
			/// Indica se o assembly deve ser salvo
			/// </summary>
			public bool SaveAssembly
			{ get; set; }
		}

		#endregion

	}

	/// <summary>
	/// Representa o código javascript compilado
	/// </summary>
	public class JavaScriptCode
	{

		public JavaScriptCode( JavaScriptCodeContext context )
		{
			this.Context = context;
		}

		public JavaScriptCodeContext Context
		{
			get;
			private set;
		}

		public bool IsClass
		{
			get { return !this.IsLambdaExpression; }
		}

		public bool IsLambdaExpression
		{
			get
			{
				return this.Context.Locals.GetFunctions().Count() == 1
					&& !this.Context.Locals.GetVariables().Any();
			}
		}

		public Type ToClass()
		{
			throw new NotImplementedException();
		}

		public TFunc ToLambda<TFunc>()
		{
			var function = this.Context.Locals.GetFunctions().First();
			if ( function.Parameters.Any() )
				return LinqExpression.Lambda<TFunc>( function.RemoveLambda(), function.Parameters ).Compile();
			else
				return LinqExpression.Lambda<TFunc>( function.RemoveLambda() ).Compile();
		}
	}


	


	internal static class JavaScriptExpressionCasts
	{

		public static ConstantExpression Cast(this ConstantWrapper node)
		{
			/************************************/
			// TODO : Criar o meu próprio tipo que irá fazer a conversão quando compilar a expressão

			switch ( node.PrimitiveType )
			{
				case PrimitiveType.Boolean:
					return LinqExpression.Constant( node.ToBoolean(), typeof( bool ) );
				case PrimitiveType.Null:
					throw new NotImplementedException();
				case PrimitiveType.Number:
					return LinqExpression.Constant( ( Number )node.ToNumber(), typeof( Number ) );
				case PrimitiveType.Other:
					break;
				case PrimitiveType.String:
				default:
					if ( node.IsParameterToRegExp )
						throw new NotImplementedException();
					else
						return LinqExpression.Constant( ( Text )node.ToString(), typeof( Text ) );
			}

			return LinqExpression.Constant( null, typeof( object ) );
		}


		public static BinaryExpression Cast( this BinaryOperator node, LinqExpression leftExp, LinqExpression rightExp )
		{
			

			switch ( node.Precedence )
			{
				case OperatorPrecedence.Additive:
					return LinqExpression.AddChecked( leftExp, rightExp );
				case OperatorPrecedence.Assignment:
					break;
				case OperatorPrecedence.BitwiseAnd:
					break;
				case OperatorPrecedence.BitwiseOr:
					break;
				case OperatorPrecedence.BitwiseXor:
					break;
				case OperatorPrecedence.Comma:
					break;
				case OperatorPrecedence.Conditional:
					break;
				case OperatorPrecedence.Equality:
					break;
				case OperatorPrecedence.FieldAccess:
					break;
				case OperatorPrecedence.Highest:
					break;
				case OperatorPrecedence.LogicalAnd:
					break;
				case OperatorPrecedence.LogicalOr:
					break;
				case OperatorPrecedence.Multiplicative:
					return LinqExpression.MultiplyChecked( leftExp, rightExp );
				case OperatorPrecedence.None:
					break;
				case OperatorPrecedence.Primary:
					break;
				case OperatorPrecedence.Relational:
					break;
				case OperatorPrecedence.Shift:
					break;
				case OperatorPrecedence.Unary:
					break;
				default:
					break;
			}

			return null;
		}
	}



	internal static class JavaScriptExtensions
	{

		/// <summary>
		/// 
		/// </summary>
		/// <param name="node"></param>
		/// <returns></returns>
		public static Type GetReturnType(this LinqExpression node)
		{
			switch ( node.NodeType )
			{
				case ExpressionType.AddChecked:
				case ExpressionType.AddAssign:
				case ExpressionType.AddAssignChecked:
				case ExpressionType.AndAssign:
				case ExpressionType.Assign:
				case ExpressionType.Divide:
				case ExpressionType.DivideAssign:
				case ExpressionType.ExclusiveOrAssign:
				case ExpressionType.LeftShiftAssign:
				case ExpressionType.ModuloAssign:
				case ExpressionType.Multiply:
				case ExpressionType.MultiplyAssign:
				case ExpressionType.MultiplyAssignChecked:
				case ExpressionType.OrAssign:
				case ExpressionType.PowerAssign:
				case ExpressionType.RightShiftAssign:
				case ExpressionType.SubtractAssign:
				case ExpressionType.SubtractAssignChecked:
				case ExpressionType.SubtractChecked:
					return ( ( BinaryExpression )node ).Type;
				case ExpressionType.Constant:
					return ( ( ConstantExpression )node ).Type;
				case ExpressionType.Convert:
				case ExpressionType.ConvertChecked:
				case ExpressionType.PostDecrementAssign:
				case ExpressionType.PostIncrementAssign:
				case ExpressionType.PreDecrementAssign:
				case ExpressionType.PreIncrementAssign:
				case ExpressionType.TypeAs:
				case ExpressionType.TypeIs:
					return ( ( UnaryExpression )node ).Type;
				case ExpressionType.IsTrue:
				case ExpressionType.IsFalse:
				case ExpressionType.Equal:
				case ExpressionType.Negate:
				case ExpressionType.NegateChecked:
				case ExpressionType.Not:
				case ExpressionType.NotEqual:
					return typeof( bool );
				case ExpressionType.Lambda:
					return ( ( LambdaExpression )node ).ReturnType;
				case ExpressionType.MemberAccess:
					return ( ( MemberExpression )node ).Type;
				case ExpressionType.Parameter:
					return ( ( ParameterExpression )node ).Type;
			}

			return typeof( object );
		}
	}

	
	

	public enum SymbolKind
	{

		Variable = 1,
		Function = 2,
		Label = 3,
		Parameter = 4
	}
}
